
package CSS::Preprocessor::Parser;

use strict;
use warnings;
use fields qw(
    tokenizer
    scope_stack
);

use Carp;

use CSS::Preprocessor::Tokenizer;
use CSS::Preprocessor::Scope;
use CSS::Preprocessor::Expression;

sub _new_with_tokenizer {
    my ($class, $tokenizer) = @_;

    my $self = fields::new($class);
    $self->{tokenizer} = $tokenizer;
    $self->{scope_stack} = [ CSS::Preprocessor::Scope->new() ];
    return $self;
}

sub parse_stringref {
    my ($class, $stringref) = @_;

    my $tokenizer = CSS::Preprocessor::Tokenizer->new_for_stringref($stringref);

    my $self = $class->_new_with_tokenizer($tokenizer);

    my @root = ();
    my $scope = $self->active_scope;

    my $peek = $tokenizer->peek_token();
    while (defined($peek)) {
        my $old_position = $tokenizer->current_position;

        push @root, CSS::Preprocessor::Expression->parse($self, CSS::Preprocessor::Expression::RULESET_CONTEXT);

        # Ensure that we don't get stuck in an infinite loop
        # FIXME: Make this actually work.
        #if ($old_position == $tokenizer->current_position) {
        #    croak "Expression parsing didn't consume any tokens";
        #}

        # Skip semicolons
        $peek = $tokenizer->peek_token();
        while (UNIVERSAL::isa($peek, 'CSS::Preprocessor::Token::Punctuation') && $peek->chars eq ';') {
            $tokenizer->get_token();
            $peek = $tokenizer->peek_token();
        }
    }

    croak "Unbalanced scopes" unless $scope == $self->active_scope;

    return CSS::Preprocessor::ExpressionBlock->new($scope, @root);
}

sub tokenizer {
    my CSS::Preprocessor::Parser $self = shift;
    return $self->{tokenizer};
}

sub active_scope {
    my CSS::Preprocessor::Parser $self = shift;
    return $self->{scope_stack}->[$#{$self->{scope_stack}}];
}

sub push_active_scope {
    my CSS::Preprocessor::Parser $self = shift;

    my $new_scope = $self->active_scope->create_child;
    push @{$self->{scope_stack}}, $new_scope;

    return $new_scope;
}

sub pop_active_scope {
    my CSS::Preprocessor::Parser $self = shift;
    my $old_scope = shift;

    die "Popped the wrong scope" unless $old_scope == $self->active_scope;

    pop @{$self->{scope_stack}};
}

sub throw_error {
    my CSS::Preprocessor::Parser $self = shift;
    my $err_string = shift;
    my $context_node = shift;

    my $position = defined($context_node) ? $context_node->position : $self->tokenizer->current_position;

    die CSS::Preprocessor::ParseError->new($err_string, $position);
}

1;

